/*-
 * Copyright (c) 2009 Jonathan Anderson
 * All rights reserved.
 *
 * WARNING: THIS IS EXPERIMENTAL SECURITY SOFTWARE THAT MUST NOT BE RELIED
 * ON IN PRODUCTION SYSTEMS.  IT WILL BREAK YOUR SOFTWARE IN NEW AND
 * UNEXPECTED WAYS.
 * 
 * This software was developed at the University of Cambridge Computer
 * Laboratory with support from a grant from Google, Inc. 
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 */

#include <sys/capability.h>

#include <errno.h>
#include <fcntl.h>

#include <iostream>
#include <sstream>

#include "cap.h"
#include "Cache.h"
#include "Client.h"

#include "other.h"

using namespace std;



string NoOp::argString() const { return ""; }
int NoOp::readArguments(Client*, struct ua_datum* d) { free(d); return 0; }
int NoOp::run(Client *client)
{
	struct ua_datum *d = ua_marshall_int(0);
	if(!d) return -1;

	int bytes = ua_send(client->socket(), d, NULL, 0);
	free(d);
	if(bytes < 0) return -1;

	return 0;
}



string CacheFile::argString() const
{
	ostringstream oss;
	oss << files.size() << " files: ";

	for(QList<File>::ConstIterator i = files.begin(); i != files.end(); i++)
		oss << "{ name \"" << i->filename().toStdString()
		    << "\", FD " << i->fd() << " } ";

	return oss.str();
}

int CacheFile::readArguments(Client *client, struct ua_datum* d)
{
	int fileCount = 0;
	if(ua_unmarshall_int(d, &fileCount) < 0) return -1;
	free(d);

	shortLived = 0;
	d = ua_recv(client->socket(), NULL, NULL);
	int ret = ua_unmarshall_int(d, &shortLived);
	free(d);
	if(ret < 0) return -1;

	for(int i = 0; i < fileCount; i++)
	{
		int fd = -1;
		unsigned int fdlen = 1;

		struct ua_datum *named = ua_recv(client->socket(), &fd, &fdlen);
		if(!named) return -1;
		if(fd < 0)
		{
			errno = EBADF;
			return -1;
		}

		char name[256];
		unsigned int namelen = sizeof(name);
		if(ua_unmarshall_string(named, name, &namelen) < 0) return -1;
		free(named);

		cap_rights_t rights;
		if(!cap_getrights(fd, &rights)) return -1;

		files << File(name, fd, rights);
	}

	return 0;
}


int CacheFile::run(Client *client)
{
	for(QList<File>::Iterator i = files.begin(); i != files.end(); i++)
	{
		Cache::instance().store(*i, shortLived);

		string token = i->capstring().toStdString();
		struct ua_datum *ret =
			ua_marshall_string(token.c_str(), strlen(token.c_str()));

		if(ua_send(client->socket(), ret, NULL, 0) < 0) return -1;

		free(ret);
	}

	return 0;
}



string RetrieveFile::argString() const { return token.toStdString(); }
int RetrieveFile::readArguments(Client *client, struct ua_datum* d)
{
	char token[512];
	unsigned int tokenlen = sizeof(token);

	if(ua_unmarshall_string(d, token, &tokenlen) < 0) return -1;

	this->token = token;
	return 0;
}


int RetrieveFile::run(Client *client)
{
	File file = Cache::instance().retrieve(token);
	int fd = file.fd();
	if(fd < 0) return -1;

	string filename = file.filename().toStdString();
	struct ua_datum *response =
		ua_marshall_string(filename.c_str(), filename.length());

	if(ua_send(client->socket(), response, &fd, 1) < 0) return -1;
	free(response);

	return 0;
}



string Stat::argString() const { return "path: " + path.toStdString(); }
int Stat::readArguments(Client *client, struct ua_datum* d)
{
	char path[256] = "";
	unsigned int pathlen = 256;

	if(ua_unmarshall_string(d, path, &pathlen) < 0) return -1;

	this->path = path;
	return 0;
}


int Stat::run(Client *client)
{
	struct stat s;
	if(stat(path.toStdString().c_str(), &s)) return 1;

	struct ua_datum *d = ua_marshall_string((char*) &s, sizeof(s));
	if(!d) return -1;

	int bytes = ua_send(client->socket(), d, NULL, 0);
	free(d);

	if(bytes < 0) return -1;
	else return 0;
}




string CheckAccess::argString() const { return "path: " + path.toStdString(); }
int CheckAccess::readArguments(Client *client, struct ua_datum* d)
{
	char path[256] = "";
	unsigned int pathlen = 256;

	int sock = client->socket();

	if(ua_unmarshall_string(d, path, &pathlen) < 0) return -1;

	int32_t mode;
	if(ua_unmarshall_int(ua_recv(sock, NULL, NULL), &mode) < 0) return -1;

	this->path = path;
	this->mode = mode;

	return 0;
}


int CheckAccess::run(Client *client)
{
	int retval = access(path.toStdString().c_str(), mode);
	struct ua_datum *d = ua_marshall_int(retval);
	if(!d) return -1;

	int bytes = ua_send(client->socket(), d, NULL, 0);
	free(d);

	if(bytes > 0) return 0;
	else return -1;
}



string OpenFile::argString() const
{
	ostringstream oss;
	oss << "path: " << path.toStdString() << ", flags = 0";

	if(flags | O_RDONLY) oss << " | O_RDONLY";

	// TODO: other flags

	return oss.str();
}

int OpenFile::readArguments(Client *client, struct ua_datum* d)
{
	char path[256] = "";
	unsigned int pathlen = 256;

	int sock = client->socket();

	if(ua_unmarshall_string(d, path, &pathlen) < 0) return -1;

	int32_t flags, umask;
	int64_t rights;
	if(ua_unmarshall_int(ua_recv(sock, NULL, NULL), &flags) < 0) return -1;
	if(ua_unmarshall_long(ua_recv(sock, NULL, NULL), &rights) < 0) return -1;
	if(ua_unmarshall_int(ua_recv(sock, NULL, NULL), &umask) < 0) return -1;

	this->path = path;
	this->flags = flags;
	this->rights = rights;
	this->umask = umask;

	return 0;
}


int OpenFile::run(Client *client)
{
	File file;

	if(path.startsWith("/cap/"))
		file = client->getFile(path.toStdString());

	else
	{
		int cap = cap_open(path.toStdString().c_str(), flags, rights, umask);
		if(cap < 0) return 1;

		file = File(path, cap, rights);
	}

	struct ua_datum *d = ua_marshall_int(1);
	if(!d) return -1;

	int bytes = ua_send(client->socket(), d, NULL, 0);
	free(d);
	if(bytes < 0) return -1;

	d = ua_marshall_string(path.toStdString().c_str(), path.length());
	if(!d) return -1;

	int32_t fd = file.fd();
	bytes = ua_send(client->socket(), d, &fd, 1);
	free(d);
	if(bytes < 0) return -1;

	return 0;
}

